package com.hubiao.spring.framework.webmvc.servlet;

import com.hubiao.spring.framework.annotation.HbAutowired;
import com.hubiao.spring.framework.annotation.HbController;
import com.hubiao.spring.framework.annotation.HbRequestMapping;
import com.hubiao.spring.framework.annotation.HbService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * Description
 *
 * @author hubiao
 * @since 2021-01-13 22:38
 */
public class HbDispatcherServlet extends HttpServlet {

    /**
     * 保存application.properties文件中得内容
     */
    private Properties contextConfig = new Properties();

    /**
     * 保存所有扫描到得类名
     **/
    private List<String> classNames = new ArrayList<>();

    /**
     * 模拟ioc容器
     **/
    private Map<String, Object> ioc = new HashMap<>();

    /**
     * 模拟handleMapping容器
     **/
    private Map<String, Method> handleMapping = new HashMap<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost( req, resp );
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6.调用  运行阶段
        try {
            doDispatch( req, resp );
        } catch (Exception e) {
            resp.getWriter().write( "<font size='25' color='blue'>500 Exception</font><br/>Details:<br/>" + Arrays.toString( e.getStackTrace() ).replaceAll( "\\[|\\]", "" )
                    .replaceAll( "\\s", "\r\n" ) + "<font color='green'><i>Copyright@GupaoEDU</i></font>" );
            e.printStackTrace();
        }
    }


    /**
     * Spring MVC 入口 初始化阶段
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        doLoadConfig( config.getInitParameter( "contextConfigLocation" ) );

        //2.扫描相关得类
        doScanner( contextConfig.getProperty( "scanPackage" ) );

        //3.初始化相关得类，并将它们加载到IOC容器种
        doInstance();

        //4.完成依赖注入
        doAutoWired();

        //5.初始化HandleMapping
        initHandleMapping();

        System.out.println( " Hu Biao Spring framework init. " );
    }

    /**
     * 加载配置文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        //直接将类路径下得spring主配置文件路径读取出来
        InputStream in = this.getClass().getClassLoader().getResourceAsStream( contextConfigLocation );
        try {
            //将读取出来得流文件放到properties对象中
            //相当于将 scanPackage = "com.hubiao.spring" 从文件中保存到内存中
            contextConfig.load( in );
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != in) {
                try {
                    //关闭流
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 扫描相关得类
     *
     * @param scanPackage ( scanPackage == com.hubiao.spring )
     */
    private void doScanner(String scanPackage) {
        //转换为文件路劲 实际上就是将 com.hubiao.spring 中得“.”转为“/”
        // url == classPath
        URL url = this.getClass().getClassLoader().getResource( "/" + scanPackage.replaceAll( "\\.", "/" ) );
        File classPath = new File( url.getFile() );
        //遍历classPath下的文件
        for (File file : classPath.listFiles()) {
            //过滤掉文件夹
            if (file.isDirectory()) {
                //如果是文件夹得话，递归遍历文件夹下得文件
                doScanner( scanPackage + "." + file.getName() );
            } else {
                //过滤掉不是class得文件
                if (!file.getName().endsWith( ".class" )) {
                    continue;
                }
                //获取类名
                String className = (scanPackage + "." + file.getName().replace( ".class", "" ));
                classNames.add( className );
            }
        }
    }

    /**
     * 初始化相关得类，并将它们加载到IOC容器种(并为DI做准备)
     */
    private void doInstance() {
        //过滤空得集合
        if (classNames.isEmpty()) {
            return;
        }

        //遍历className
        for (String className : classNames) {
            try {
                //利用反射来获取Class对象
                Class<?> clazz = Class.forName( className );

                //初始化加了注解得类，这里只做了@Controller与@Service 注解参考案例
                if (clazz.isAnnotationPresent( HbController.class )) {
                    //初始化对象
                    Object instance = clazz.newInstance();
                    //获取类名当作map中得key
                    String beanName = toLowerFirstCase( clazz.getSimpleName() );
                    //将类存放到IOC容器中
                    ioc.put( beanName, instance );

                } else if (clazz.isAnnotationPresent( HbService.class )) {
                    //1.自定义得beanName
                    HbService service = clazz.getAnnotation( HbService.class );
                    String beanName = service.value();

                    //2.默认类名首字母小写得beanName
                    if ("".equals( beanName.trim() )) {
                        beanName = toLowerFirstCase( clazz.getSimpleName() );
                    }
                    //初始化对象
                    Object instance = clazz.newInstance();
                    //将类存放到IOC容器中
                    ioc.put( beanName, instance );

                    //3.根据类名自动赋值，投机取巧得方式
                    for (Class i : clazz.getInterfaces()) {
                        if (ioc.containsKey( i.getName() )) {
                            throw new Exception( "The “" + i.getName() + "” is exist " );
                        }
                        ioc.put( i.getName(), instance );
                    }

                } else {
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 完成依赖注入
     */
    private void doAutoWired() {
        //如果ioc容器中没有值，则过滤
        if (ioc.isEmpty()) {
            return;
        }

        //遍历ioc对象
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //declared 所有得，公开的 表示全部的得属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            //遍历所有得属性，然后获取有@Autowired注解得属性
            for (Field field : fields) {
                if (!field.isAnnotationPresent( HbAutowired.class )) {
                    continue;
                }
                HbAutowired autowired = field.getAnnotation( HbAutowired.class );
                //1.自定义beanName
                String beanName = autowired.value().trim();
                //2.默认类名首字母小写得beanName
                if ("".equals( beanName )) {
                    beanName = field.getType().getName();
                }
                //public 修饰符以外得使用暴力访问
                field.setAccessible( true );
                try {
                    //利用反射给field赋值
                    field.set( entry.getValue(), ioc.get( beanName ) );
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

        }

    }

    /**
     * 初始化HandleMapping
     */
    private void initHandleMapping() {
        //如果ioc容器中没有值，则过滤
        if (ioc.isEmpty()) {
            return;
        }

        //遍历ioc容器中得对象
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取对象
            Class<?> clazz = entry.getValue().getClass();
            //过滤没有@HbRequestMapping注解得对象
            if (!clazz.isAnnotationPresent( HbRequestMapping.class )) {
                continue;
            }

            try {
                //保存写在【类上面@HbRequestMapping("/demo")】注解得路径
                HbRequestMapping requestMapping = clazz.getAnnotation( HbRequestMapping.class );
                String baseUrl = requestMapping.value();
                //遍历所有public修饰得方法，然后获取到【方法上面得@HbRequestMapping("/list")】得路径
                for (Method method : clazz.getMethods()) {
                    if (!method.isAnnotationPresent( HbRequestMapping.class )) {
                        continue;
                    }

                    HbRequestMapping annotation = method.getAnnotation( HbRequestMapping.class );
                    String url = (baseUrl + "/" + annotation.value()).replaceAll( "/+", "/" );

                    handleMapping.put( url, method );
                }


            } catch (Exception e) {
                e.printStackTrace();
            }


        }
    }

    /**
     * 调用  运行阶段
     *
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //绝对路径
        String url = req.getRequestURI();
        //相对路径
        String contextPath = req.getContextPath();
        url = url.replaceAll( contextPath, "" ).replaceAll( "/+", "/" );
        if (!handleMapping.containsKey( url )) {
            resp.getWriter().write( "404 Not Found!!!" );
        }

        Method method = this.handleMapping.get( url );
        //获取当前类得类名
        String beanName = method.getDeclaringClass().getSimpleName();
        //获取参数集合
        Map<String, String[]> params = req.getParameterMap();
        //调用方法   第一个参数是方法得【类对象】 第二个参数是【方法入参】
        method.invoke( ioc.get( beanName ), new Object[]{req, resp, params.get( "name" )[0]} );

    }


    /**
     * 将类名首字母转为小写
     *
     * @param simpleName
     * @return
     */
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        //根据ASCII码得值 大小写相差32 且大写得字母得ASCII码值比小写得小
        chars[0] += 32;
        return String.valueOf( chars );
    }


}
